package dream

import (
	"bytes"
	"context"
	"encoding/binary"
	"encoding/json"
	"errors"
	"fmt"
	"net"
	"os"
	"sync"
	"time"
	"unsafe"
)

type IAutoConnInterface interface {
	OnReady(id int)
	OnPackage(data []byte, xy XY)
	OnClose()
}

type IAutoConnHandler interface {
	OnPackage(data []byte, xy XY)
}

type AutoConnFunc func(data []byte, xy XY)

func (f AutoConnFunc) OnPackage(data []byte, xy XY) {
	f(data, xy)
}

type sAutoAocket struct {
	dialCancel       func()
	con              net.Conn
	lock             *sync.Mutex
	sleeptime        uint32
	cbAutoConn       IAutoConnInterface
	ch_sendlist      chan []byte
	boked            bool
	sendMaxBlockTime uint32
	breakcount       int
	addr             string
	auth             string
	sendlastfail     error
	recvlastfail     error
	bstoped          bool
	name             string
	serviceID        int
	processID        int
	svrType          int
}

func (p *sAutoAocket) gethdlen() int { //headerlen        int //0,1,2,137
	return 137
}

func AutoConnect(n string, addrs string, auth string, svrType int, serviceid int, cbAutoConn IAutoConnInterface) IAutoConnect {
	Assert(cbAutoConn != nil, "AutoConnect::handler nil")
	p := &sAutoAocket{
		cbAutoConn:       cbAutoConn,
		sleeptime:        autosettings.sleeptime,
		ch_sendlist:      nil,
		boked:            false,
		breakcount:       0,
		sendMaxBlockTime: 3000,
		dialCancel:       nil,
		con:              nil,
		lock:             &sync.Mutex{},
		addr:             addrs,
		auth:             auth,
		bstoped:          false,
		name:             n,
		serviceID:        serviceid,
		processID:        os.Getpid(),
		svrType:          svrType,
	}
	return p
}
func (p *sAutoAocket) Run(wg *sync.WaitGroup) {
	if wg != nil {
		wg.Add(1)
	}
	go p.run(wg)
}

type IAutoConnect interface {
	IRun
	SendXY(xy XY, pack []byte) int
	IStop
}

func (p *sAutoAocket) SendXY(xy XY, pack []byte) int {
	Assert(pack != nil, "AutoAocket::SendPackage nil")
	AssertImposible(p.gethdlen() == 0 && len(pack) > 0xff, "AutoAocket::SendPackage 0 limit")
	AssertImposible(p.gethdlen() == 1 && len(pack) > 0xff, "AutoAocket::SendPackage 1 limit")
	AssertImposible(p.gethdlen() == 2 && len(pack) > 0xffff, "AutoAocket::SendPackage 2 limit")
	//if atomic.LoadInt32(&p.badState) != 0 {
	//	return -1
	//}
	p.lock.Lock()
	cansend := true
	if p.dialCancel != nil || p.con == nil || p.bstoped || !p.boked {
		cansend = false
	}
	p.lock.Unlock()
	if !cansend {
		return -1
	}
	timeout := time.NewTimer(time.Microsecond * time.Duration(p.sendMaxBlockTime))
	pk := allocPackWithInstall(true, xy, pack, p.gethdlen())
	select {
	case p.ch_sendlist <- pk:
	case <-timeout.C:
		return 1
	}
	return 0
}

func (p *sAutoAocket) Stop() {
	//atomic.StoreInt32(&p.stoped, 1)
	p.lock.Lock()
	p.bstoped = true
	if p.dialCancel != nil {
		p.dialCancel()
		p.dialCancel = nil
	}
	if p.con != nil {
		p.con.Close()
		p.con = nil
	}
	p.lock.Unlock()
}

type sAutoSettings struct {
	rtimeouts    uint32
	wtimeouts    uint32
	headerDeflen int
	sleeptime    uint32
	printinfo    bool
}

var autosettings sAutoSettings

func init() {
	autosettings.rtimeouts = 8 * 60 * 60
	autosettings.wtimeouts = 60
	autosettings.headerDeflen = 2
	autosettings.sleeptime = 3
}

func (p *sAutoAocket) run(wg *sync.WaitGroup) {
	defer func() { CrashSnap(recover()) }()
	for {
		if stoped := p.runonce(); stoped {
			Logi(LOG_KERL, " ...AutoConnect ", p.addr, " exit")
			break
		}
		if autosettings.printinfo {
			fmt.Println(" AutoAocket will re connect after ", p.sleeptime, " second")
		}
		time.Sleep(time.Duration(p.sleeptime) * time.Second)
	}

	if wg != nil {
		wg.Done()
	}
}

func (p *sAutoAocket) runSend(c net.Conn, wg *sync.WaitGroup) {
	defer func() { CrashSnap(recover()) }()

	for {
		buf := <-p.ch_sendlist
		if buf == nil { //nil pack or chan close
			break
		}
		err := loopSend(p.con, buf, autosettings.wtimeouts)
		if err != nil {
			p.sendlastfail = err
			break
		}
		//freePack(buf)
	}

	wg.Done()
}

func (p *sAutoAocket) runRecv(c net.Conn) {
	p.cbAutoConn.OnReady(0)
	header := make([]byte, 7)
	for {
		len, err := recvPackageHeader(c, p.gethdlen(), autosettings.rtimeouts, header)
		if err != nil {
			p.recvlastfail = err
			break
		}
		var xy XY = 0
		if true {
			if len < uint32(unsafe.Sizeof(xy)) {
				p.recvlastfail = errors.New("XY limit")
				break
			}
		}
		//len += uint32(unsafe.Sizeof(xy))
		var pack []byte = nil
		if len > 0 {
			pack = allocPack(len)
			var err error
			if isValidHenlen(p.gethdlen()) {
				err = loopRecv(c, pack, autosettings.rtimeouts)
			} else {
				var read int
				read, err = tryRecv(c, pack, autosettings.rtimeouts)
				pack = pack[0:read]
			}
			if err != nil {
				p.recvlastfail = err
				break
			}
		}
		if true {
			if len < uint32(unsafe.Sizeof(xy)) {
				p.recvlastfail = errors.New("XY limit")
				break
			}
			io := bytes.NewBuffer(pack)
			binary.Read(io, binary.LittleEndian, &xy)
		}
		p.cbAutoConn.OnPackage(pack[int(unsafe.Sizeof(xy)):], xy)
	}
	p.cbAutoConn.OnClose()
}

func (p *sAutoAocket) runonce() bool {
	d := &net.Dialer{Timeout: time.Duration(10) * time.Second}
	ctx, cc := context.WithCancel(context.TODO())
	p.lock.Lock() //////////////////////////////////
	Assert(p.con == nil, "AutoAocket::conn not nil")
	Assert(p.dialCancel == nil, "AutoAocket::dialCancel not nil")
	stoped := p.bstoped
	p.boked = false
	p.dialCancel = cc
	p.lock.Unlock() //////////////////////////////////
	if stoped {
		return stoped
	}

	Logi(LOG_KERL, "AutoAocket ", p.name, " connect ", p.addr, " ...")
	c, err := d.DialContext(ctx, "tcp", p.addr)

	p.lock.Lock() //////////////////////////////////
	p.dialCancel = nil
	stoped = p.bstoped
	if !stoped && err == nil {
		p.con = c
		p.boked = false
	}
	p.lock.Unlock() //////////////////////////////////
	if err != nil {
		return stoped
	}
	if stoped {
		return stoped
	}

	if p.ch_sendlist != nil {
		close(p.ch_sendlist)
	}
	p.ch_sendlist = make(chan []byte, 100)

	//认证过程
	Logi(LOG_KERL, "AutoAocket ", p.name, " connect ", p.addr, " ok")

	j := NewJSON()
	j["type"] = p.svrType
	j["id"] = p.serviceID
	j["processid"] = p.processID
	j["sign"] = p.auth
	b, _ := json.Marshal(j)
	pk := allocPackWithInstall(true, 30001, b, p.gethdlen())
	loopSend(p.con, pk, autosettings.wtimeouts)

	var closeconn net.Conn
	p.lock.Lock() //////////////////////////////////
	stoped = p.bstoped
	if stoped || err != nil {
		closeconn = p.con
		p.con = nil
		p.boked = false
	}
	p.lock.Unlock() //////////////////////////////////
	if closeconn != nil {
		closeconn.Close()
		closeconn = nil
	}
	if err != nil {
		Logi(LOG_KERL, "AutoAocket ", p.name, " send auth fail:", err)
		return stoped
	}
	if stoped {
		return stoped
	}

	header := make([]byte, 7)
	err = nil
	var xy XY = 0
	var pack []byte = nil
	var dpack []byte = nil
	for {
		var len uint32
		len, err = recvPackageHeader(c, p.gethdlen(), autosettings.rtimeouts, header)
		if err != nil {
			break
		}
		if true {
			if len < uint32(unsafe.Sizeof(xy)) {
				err = errors.New("XY limit")
				break
			}
		}
		if len > 0 {
			pack = allocPack(len)
			err = loopRecv(c, pack, autosettings.rtimeouts)
			if err != nil {
				break
			}
		}
		if true {
			if len < uint32(unsafe.Sizeof(xy)) {
				err = errors.New("XY limit")
				break
			}
			io := bytes.NewBuffer(pack)
			binary.Read(io, binary.LittleEndian, &xy)
		}
		dpack = pack[int(unsafe.Sizeof(xy)):]
		break
	}
	var sid int64
	var errori int64
	if err == nil {
		if xy != 30002 || dpack == nil || len(dpack) == 0 {
			err = errors.New("Data err")
		} else {
			jr := NewJSON()
			err = json.Unmarshal(dpack, &jr)
			if err == nil {
				jrd := JSONRead{
					JSONT: jr,
				}
				sid = jrd.Num("id")
				errori = jrd.Num("error")
				if sid == 0 || errori != 0 {
					err = errors.New("ret serverid err")
				}
			}
		}
	}
	if pack != nil {
		freePack(pack)
	}
	p.lock.Lock() //////////////////////////////////
	stoped = p.bstoped
	if stoped || err != nil {
		closeconn = p.con
		p.con = nil
		p.boked = false
	} else {
		p.boked = true
	}
	p.lock.Unlock() //////////////////////////////////
	if closeconn != nil {
		closeconn.Close()
		closeconn = nil
	}
	if err != nil {
		Logi(LOG_KERL, "AutoAocket ", p.name, " recv auth fail:", err)
		return stoped
	}
	if stoped {
		return stoped
	}
	Logi(LOG_KERL, "AutoAocket ", p.name, " authed(", sid, ",", errori, ")")

	swg := &sync.WaitGroup{}
	swg.Add(1)
	go p.runSend(c, swg)
	p.runRecv(c)

	p.lock.Lock() //////////////////////////////////
	stoped = p.bstoped
	if p.con != nil {
		p.con.Close()
		p.con = nil
	}
	p.boked = false
	//if _, ok := <-p.ch_sendlist; ok {
	close(p.ch_sendlist)
	p.ch_sendlist = nil
	//}
	p.lock.Unlock() //////////////////////////////////

	swg.Wait()
	return stoped
}
